"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.watch = void 0;
const path_1 = __importDefault(require("path"));
const chokidar_1 = __importDefault(require("chokidar"));
const p_queue_1 = __importDefault(require("p-queue"));
const debounce_1 = __importDefault(require("debounce"));
const handleFieldsJS_1 = require("./handleFieldsJS");
const uploadFolder_1 = require("./uploadFolder");
const ignoreRules_1 = require("../ignoreRules");
const fileMapper_1 = require("../fileMapper");
const fileMapper_2 = require("../../api/fileMapper");
const escapeRegExp_1 = require("../escapeRegExp");
const path_2 = require("../path");
const notify_1 = require("../notify");
const themes_1 = require("./themes");
const logger_1 = require("../logger");
const lang_1 = require("../../utils/lang");
const HubSpotHttpError_1 = require("../../models/HubSpotHttpError");
const errors_1 = require("../../errors");
const i18nKey = 'lib.cms.watch';
const queue = new p_queue_1.default({
    concurrency: 10,
});
function _notifyOfThemePreview(filePath, accountId) {
    if (queue.size > 0)
        return;
    const previewUrl = (0, themes_1.getThemePreviewUrl)(filePath, accountId);
    if (!previewUrl)
        return;
    logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.notifyOfThemePreview`, {
        previewUrl,
    }));
}
const notifyOfThemePreview = (0, debounce_1.default)(_notifyOfThemePreview, 1000);
const defaultOnUploadFileError = (file, dest, accountId) => (error) => {
    const uploadFailedMessage = (0, lang_1.i18n)(`${i18nKey}.uploadFailed`, {
        file,
        dest,
    });
    logger_1.logger.debug(uploadFailedMessage);
    throw new HubSpotHttpError_1.HubSpotHttpError(uploadFailedMessage, {
        cause: error,
    }, {
        accountId,
        request: dest,
        payload: file,
    });
};
async function uploadFile(accountId, file, dest, options, cmsPublishMode = null, onUploadFileError = defaultOnUploadFileError) {
    const src = options.src;
    const absoluteSrcPath = path_1.default.resolve((0, path_2.getCwd)(), file);
    const themeJsonPath = (0, themes_1.getThemeJSONPath)(absoluteSrcPath);
    const projectRoot = themeJsonPath
        ? path_1.default.dirname(themeJsonPath)
        : path_1.default.dirname((0, path_2.getCwd)());
    const convertFields = (0, handleFieldsJS_1.isConvertableFieldJs)(src, file, options.commandOptions.convertFields);
    if (!(0, path_2.isAllowedExtension)(file) && !convertFields) {
        logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.skipUnsupportedExtension`, { file }));
        return;
    }
    if ((0, ignoreRules_1.shouldIgnoreFile)(file)) {
        logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.skipIgnoreRule`, { file }));
        return;
    }
    let fieldsJs = undefined;
    if (convertFields) {
        fieldsJs = await new handleFieldsJS_1.FieldsJs(projectRoot, absoluteSrcPath, undefined, options.fieldOptions).init();
        if (fieldsJs.rejected)
            return;
        // Ensures that the dest path is a .json. The user might pass '.js' accidentally - this ensures it just works.
        dest = (0, path_2.convertToUnixPath)(path_1.default.join(path_1.default.dirname(dest), 'fields.json'));
    }
    const fileToUpload = convertFields && fieldsJs?.outputPath ? fieldsJs.outputPath : file;
    logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.uploadAttempt`, { file, dest }));
    const apiOptions = (0, fileMapper_1.getFileMapperQueryValues)(cmsPublishMode, options);
    queue.add(() => {
        return (0, fileMapper_2.upload)(accountId, fileToUpload, dest, apiOptions)
            .then(() => {
            logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.uploadSuccess`, { file, dest }));
            notifyOfThemePreview(file, accountId);
        })
            .catch(() => {
            logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.uploadFailed`, { file, dest }));
            logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.uploadRetry`, { file, dest }));
            return (0, fileMapper_2.upload)(accountId, file, dest, apiOptions).catch(onUploadFileError(file, dest, accountId));
        });
    });
}
async function deleteRemoteFile(accountId, filePath, remoteFilePath) {
    if ((0, ignoreRules_1.shouldIgnoreFile)(filePath)) {
        logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.skipIgnoreRule`, { file: filePath }));
        return;
    }
    logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.deleteAttempt`, { remoteFilePath }));
    return queue.add(() => {
        return (0, fileMapper_2.deleteFile)(accountId, remoteFilePath)
            .then(() => {
            logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.deleteSuccess`, { remoteFilePath }));
            notifyOfThemePreview(filePath, accountId);
        })
            .catch(error => {
            logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.deleteFailed`, {
                remoteFilePath,
            }));
            if ((0, errors_1.isHubSpotHttpError)(error)) {
                error.updateContext({
                    accountId,
                    request: remoteFilePath,
                });
            }
            throw error;
        });
    });
}
function watch(accountId, src, dest, { cmsPublishMode, remove, disableInitial, notify, commandOptions, filePaths, }, postInitialUploadCallback = null, onUploadFolderError, onQueueAddError, onUploadFileError) {
    const regex = new RegExp(`^${(0, escapeRegExp_1.escapeRegExp)(src)}`);
    if (notify) {
        (0, ignoreRules_1.ignoreFile)(notify);
    }
    const watcher = chokidar_1.default.watch(src, {
        ignoreInitial: true,
        ignored: (file) => (0, ignoreRules_1.shouldIgnoreFile)(file),
    });
    function getDesignManagerPath(file) {
        const relativePath = file.replace(regex, '');
        return (0, path_2.convertToUnixPath)(path_1.default.join(dest, relativePath));
    }
    if (!disableInitial) {
        // Use uploadFolder so that failures of initial upload are retried
        const uploadFolderPromise = (0, uploadFolder_1.uploadFolder)(accountId, src, dest, {}, commandOptions, filePaths, cmsPublishMode || null).then(result => {
            logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.folderUploadSuccess`, {
                src,
                dest,
                accountId,
            }));
            if (postInitialUploadCallback) {
                postInitialUploadCallback(result);
            }
        });
        if (onUploadFolderError) {
            uploadFolderPromise.catch(onUploadFolderError);
        }
    }
    watcher.on('ready', () => {
        logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.ready`, { src }));
    });
    watcher.on('add', async (filePath) => {
        const destPath = getDesignManagerPath(filePath);
        const uploadPromise = uploadFile(accountId, filePath, destPath, {
            src,
            commandOptions,
        }, cmsPublishMode, onUploadFileError);
        (0, notify_1.triggerNotify)(notify, 'Added', filePath, uploadPromise);
    });
    if (remove) {
        const deleteFileOrFolder = (type) => (filePath) => {
            // If it's a fields.js file that is in a module folder or the root, then ignore because it will not exist on the server.
            if ((0, handleFieldsJS_1.isConvertableFieldJs)(src, filePath, commandOptions.convertFields)) {
                return;
            }
            const remotePath = getDesignManagerPath(filePath);
            if ((0, ignoreRules_1.shouldIgnoreFile)(filePath)) {
                logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.skipIgnoreRule`, { file: filePath }));
                return;
            }
            logger_1.logger.debug((0, lang_1.i18n)(`${i18nKey}.deleteAttemptWithType`, {
                type,
                remoteFilePath: remotePath,
            }));
            const queueAddPromise = queue.add(() => {
                const deletePromise = deleteRemoteFile(accountId, filePath, remotePath).then(() => {
                    logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.deleteSuccessWithType`, {
                        type,
                        remoteFilePath: remotePath,
                    }));
                });
                if (onQueueAddError) {
                    queueAddPromise.catch(onQueueAddError);
                }
                (0, notify_1.triggerNotify)(notify, 'Removed', filePath, deletePromise);
                return deletePromise;
            });
        };
        watcher.on('unlink', deleteFileOrFolder('file'));
        watcher.on('unlinkDir', deleteFileOrFolder('folder'));
    }
    watcher.on('change', async (filePath) => {
        const destPath = getDesignManagerPath(filePath);
        const uploadPromise = uploadFile(accountId, filePath, destPath, {
            src,
            commandOptions,
        }, cmsPublishMode, onUploadFileError);
        (0, notify_1.triggerNotify)(notify, 'Changed', filePath, uploadPromise);
    });
    return watcher;
}
exports.watch = watch;
